package com.example.ssyinitlearn.filter;

import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;

/**
 * 自定义的登录过滤器，目的为可以使用JSON进行登录
 * 直接继承默认的表单过滤器 UsernamePasswordAuthenticationFilter，将其获取用户名/密码的部分更改为从JSON中获取即可
 */
public class JsonLoginFilter extends UsernamePasswordAuthenticationFilter {
    /**
     * 自己实现的过滤器，必须绑定使用的 AuthenticationManager。
     * 在 WebSecurityConfigurerAdapter的覆写 authenticationManagerBean() 方法中返回的 AuthenticationManager
     *   会被自动绑定的默认的 UsernamePasswordAuthenticationFilter上，但是自己实现的过滤器是不会自动绑定的，需要手动。
     * <p>
     * 就如同之前讲过的一样:
     *   1. 直接在这里的构造方法中，制作要用的 AuthenticationManager，然后使用 super.setAuthenticationManager()进行绑定。
     *      然后这个自定义Filter可以整个使用 @Service注解，最后在  WebSecurityConfigurerAdapter中直接用 @Autowired进行注入。
     *   2. 或者就先不绑定，构造函数直接不写。在要用这个Filter的时候，用New 的方法新建，然后绑定内容，最后用@Bean注解也是可以的。
     */
//    public JsonLoginFilter() {
//        super.setAuthenticationManager();
//    }

    /**
     * 核心的方法就是 attemptAuthentication()，前端提交的请求最先进入该方法
     *
     * @param request
     * @param response
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        // 请求必须是 POST，不能是 GET
        if (!request.getMethod().equals("POST")) {
            // 使用 HttpServletRequest.getMethod() 方法获取请求的类型
            System.out.println("认证请求不支持" + request.getMethod());
            throw new AuthenticationServiceException("认证请求不支持" + request.getMethod());
        }

        // 原生使用form表单提交时，直接使用 HttpServletRequest.getParameter(name) 即可获取对应的提交参数
        // 而现在要求请求类型需要是 JSON。

        // 判断方式很简单，之前我们后端往前端传数据时，如果手动指定数据格式(而不是使用@ResponseBody这种)，
        // 就会写 httpServletResponse.setContentType("application/json;charset=utf-8");
        // 所以这里就是判断 ContentType的值是不是这个就可以。
        // spring提供了枚举类来存储ContextType。你既可以手写 "application/json;charset=utf-8"，也可以直接使用 MediaType.APPLICATION_JSON_VALUE
        String contentType = request.getContentType();
        if (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)
                || request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_UTF8_VALUE)) {
            // 用一个哈希表映射JSON数据
            HashMap<String, String> userInfo = new HashMap<>();
            try {
                // 使用FastJSON从request中获取输入流直接全转换成HashMap
                userInfo = JSON.parseObject(request.getInputStream(), HashMap.class);
                // userInfo = new ObjectMapper().readValue(request.getInputStream(), HashMap.class);
                // 原先表单登录时直接 request.getParameter(getUsernameParameter())，现在只不过是从哈希表中获取键值对
                String username = userInfo.get(getUsernameParameter());
                String password = userInfo.get(getPasswordParameter());
                // 获取来之后和 UsernamePasswordAuthenticationFilter完全相同，因为就是根据 username和password构建 Authentication
                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
                this.setDetails(request, authRequest);
                return this.getAuthenticationManager().authenticate(authRequest);
            } catch (IOException e) {
                e.printStackTrace();
                throw new AuthenticationServiceException("JsonLoginFilter过滤器未能从登录JSON中获取登录数据");
            }
        } else {
            System.out.println("JsonLoginFilter过滤器仅支持JSON登录，当前请求格式:" + request.getContentType());
            throw new AuthenticationServiceException("JsonLoginFilter过滤器仅支持JSON登录，当前请求格式:" + request.getContentType());
        }
    }

}
